Skip to content

fix(core): capture pages load only the clips on screen - #7402

Merged
miguel-heygen merged 2 commits into
mainfrom
dcrashapp/capture-no-prefetch
Nov 8, 2026
Merged

miguel-heygen merged 2 commits into
mainfrom
dcrashapp/capture-no-prefetch

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator

What

A capture page (hf-capture: posters, hover frames, and hyperframes check) now loads only the clips on screen at its seek target. It no longer prefetches upcoming clips or holds clips due at the loop start, and a clip the seek leaves is released even when fully buffered.

Why

A capture page seeks and never plays, but it ran the same look-ahead as a playing preview: up to three upcoming clips prefetched, plus clips kept around the playhead. With large footage each hidden capture window held several decoded video streams it never shows. A host that keeps a few capture windows open at once (a poster, a hover window, stills) runs out of renderer memory on long, heavily cut 4K edits.

Measured in a hidden Electron window on a 1080x1920, 30 s composition of 65 cuts taken from six 4K h264 sources, swapping only the runtime script (main at 7ad2be9 vs this branch), after the window settled:

main this PR
ready (__renderReady) 0.9 to 5.7 s 0.47 to 0.59 s
at start 1.2 to 1.5 GB, 4 to 5 videos with data 0.43 GB, 1 video
after seek to 5 s 1.6 to 3.7 GB, 6 to 13 videos 0.49 to 0.50 GB, 1 video
after seek to 8 s 1.4 to 3.6 GB, 5 to 13 videos 0.52 to 0.53 GB, 1 video

The one video held is the cut on screen at each seek target.

Related work

Refs the capture-mode media rules in timedClipHide.ts (images were already eager on capture pages; this is the media side).

How

  • capturePageIn(doc) in studioPreviewMark.ts is the one place that reads the capture marker; timedClipHide.ts now uses it instead of its own copy.
  • In the media pass (init.ts), loadsAhead is false on a capture page, which passes upcoming = false and forLoop = false to preloadNearPlayhead, so only a visible clip is armed, and releases a clip that leaves with releaseMedia(el, true).
  • The live preview and the player are unchanged: the new branch runs only when the capture marker is present.

Test plan

fails without the fix: packages/core/src/runtime/entry.test.ts › "in a hidden capture page only the clip on screen loads, and the one a seek left is freed" (with only init.ts at the merge-base it gets [true, true, false] for [true, false, false]).

  • Unit tests added/updated: entry.test.ts "in a hidden capture page only the clip on screen loads, and the one a seek left is freed". Fails on main (armed(now, soon, later) is [true, true, false], the upcoming clip loads); passes here. Verified by reverting only init.ts: red.
  • packages/core runtime suite: 2982 passed, 3 runs in a row; core tsc --noEmit, oxfmt and oxlint clean on the changed files.
  • packages/cli check tests (check, checkBrowser, checkBrowser.chromium, checkSweep.chromium, publishProxyBake): 265 passed, 4 skipped, as on main. studio-server preview route: 153 passed.
  • Manual: the table above, in a hidden Electron window with the built runtime swapped per window.
  • Not measured: hyperframes check memory end to end on a long composition, and macOS (the window measurement ran on Linux).
  • Comments follow CONTRIBUTING.md "Comments".

Size

46 lines on purpose: one rule at the one media-pass call site, the marker read moved to one helper, and its test. Nothing open in this area could carry it.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes at 0376faa0. The code looks right to me. Two gate items are blocking.

Blocking

  1. The body has no fails without the fix: line. This is a fix PR, so it needs one line naming the test that fails without the fix. The Test plan does describe the failure, but not on the required line. Add, for example:
    fails without the fix: packages/core/src/runtime/entry.test.ts › "in a hidden capture page only the clip on screen loads, and the one a seek left is freed"
    I checked the claim locally. With only init.ts reverted to the merge-base 7ad2be96, that test fails with expected [ true, true, false ] to deeply equal [ true, false, false ]. At the head it passes, and so does the rest of entry.test.ts (57/57).
  2. The Comments workflow fails on this head (comment-ratchet, "A package file's comment share may not rise"):
    • packages/core/src/runtime/init.ts: 900 → 901 comment lines. The new // A capture page seeks and never plays… line is the one that tips it.
    • packages/core/src/studioPreviewMark.ts: 0 → 2 comment lines (the JSDoc on capturePageIn).
      Fix: cut them, or move the explanation into the name. For example, capturePageIn already says most of it, and the rule could sit in a named const such as const loadsAhead = !capturePage.

Checked, no issue

  • capturePageIn matches the marker read it replaces in timedClipHide.ts (same meta[name=…] selector and content === "capture"). The pages that set the marker are the studio-server preview route and checkBrowser.
  • The live preview and the player don't change: every new branch is behind capturePage.
  • On a capture page, releasing with force = true only frees a clip that was already decided and has now left the screen, which is what the PR intends.

Non-blocking

  • As the body notes, hyperframes check hasn't been measured end to end. Now that nothing is prefetched, every check seek onto a new cut starts a cold load. That's worth one timing run on a heavily cut film, to confirm check's wall time doesn't regress where memory improves.

I'll re-review once both blockers are fixed on a new head.

@github-actions

github-actions Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 110 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

Unstable (1)

  • seqfastkeys-none-px-r0-root-z100: tracking 0.06, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve at c6ab3a66. Both blockers from my earlier review are fixed.

  • fails without the fix: line: it's in the body now and names entry.test.ts › "in a hidden capture page only the clip on screen loads, and the one a seek left is freed". I checked it again at this head. With only init.ts at the merge-base 7ad2be96, the test fails with [true, true, false] vs [true, false, false]. At the head, entry.test.ts passes 57/57.
  • Comment ratchet: the comments are gone, and the rule now lives in const loadsAhead = !capturePageIn(document). Between 0376faa0 and this head the only change is that rename, with logic identical under De Morgan (capturePage → !loadsAhead). The Comments workflow now passes.
  • CI on this head: all workflows passed (CI, Comments, regression, preview-regression, Windows render, Player perf/Safari perf/Safari audio, CodeQL, PR captures). The cancelled duplicates were superseded by later runs on the same head.

Still non-blocking, as before: one end-to-end hyperframes check timing run on a heavily cut film, to confirm wall time doesn't regress now that nothing is prefetched.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 8, 2026
Merged via the queue into main with commit 12906e0 Nov 8, 2026
135 of 173 checks passed
@miguel-heygen
miguel-heygen deleted the dcrashapp/capture-no-prefetch branch November 8, 2026 12:37
This was referenced Nov 8, 2026
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 9, 2026
Port the adopted upstream fixes from f5bde95 to the current upstream tip:
heygen-com#7375 multipart cloud upload, heygen-com#7382 minParallelChunks, heygen-com#7349 HLG header SEI,
heygen-com#7369 skip unmounted compositions in lint/check, heygen-com#7381 filesystem-path media
in the cloud archive, heygen-com#7383 Safari cut-point pre-roll revert, heygen-com#7373 clock
steering between audio time updates, heygen-com#7384 injector test isolation, heygen-com#7378
preview seek retention, heygen-com#7390 virtual timer schedule per worker, heygen-com#7394 CDN
script failure naming, heygen-com#7395 menu row radius, heygen-com#7119 decode timeout caller
test, heygen-com#7397 authored tween order, heygen-com#7402 capture page clip loading, heygen-com#7412 muted
video audio track at cuts, heygen-com#6150 template root variable test, heygen-com#7442 held tail
seek once.

Deliberate local differences: heygen-com#7067's audio-start hold is not carried into
clock.ts, mountedSources comes from heygen-com#6827 as a helper only, cloud/_gen/types.ts
omits the image-request types the fork does not generate, and the lint
symlink test skips through symlinkOrSkip where the account cannot create links.

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 9, 2026
The 1a9bb89 port of heygen-com#7402 and heygen-com#7378 brought in releaseMedia(el, flag)
with window.__HF_RELEASE_PASSED_MEDIA (heygen-com#6929, needs the heygen-com#6785 buffered
guard) and registerSeekCompletion(landed, "image-decode") (the SeekHold
union from heygen-com#6596). Both upstream commits stay deferred, so typecheck:runtime
failed in the Windows render verification workflow. The fork's
releaseMedia already frees every clip, so the call keeps its behaviour.

Add tsconfig.runtime.json to the pre-commit typecheck, which only covered
tsconfig.json (it excludes src/runtime), and pin it in the hook test.

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant